package com.es;


import cn.hutool.json.JSONUtil;
import com.es.handler.ElasticsearchException;
import com.es.handler.EsResource;
import com.es.properties.EsProperties;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author: 小杰大人
 * @Version: 1.0.0
 * @Description: Es基本的对于 索引的增查删 文档数据的CRUD ，查询的api，以及高亮展示，分页，排序，叶子查询
 * @DateTime: 2024/6/13 21:43
 **/
@Slf4j
@Getter
public class EsTool implements InitializingBean {

    private RestHighLevelClient client;

    @Resource
    private EsProperties properties;

    @Override
    public void afterPropertiesSet() throws Exception{
        client = new RestHighLevelClient(RestClient.builder(HttpHost.create(properties.getHost())));
        log.info("Elasticsearch Client init : {}", this.client);
    }



    /**
     * 创建索引库
     *
     * @param indexName 索引库名称
     * @param source    结构
     * @return 是否创建成功
     */
    public boolean createIndex(String indexName, String source) {
        // 判断当前索引库是否存在
        if (isIndexExist(indexName)) {
            return true;
        }
        // 创建索引库
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
        createIndexRequest.source(source, XContentType.JSON);
        try {
            CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            if (createIndexResponse.isAcknowledged()) {
                return true;
            }
        } catch (Exception e) {
            log.error("创建索引库失败：索引库名称：{}，源数据：{}", indexName, source);
        }
        return false;
    }

    /**
     * 删除指定索引库
     *
     * @param indexName 索引库名称
     * @return true 成功 false 失败
     */
    public boolean removeIndex(String indexName) {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
        try {
            AcknowledgedResponse response = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
            if (response.isAcknowledged()) {
                return true;
            }
        } catch (Exception e) {
            log.error("删除索引库失败：索引库名称：{}，失败原因：{}", indexName, e.getMessage());
        }
        return false;
    }

    /**
     * 校验索引库是否已经存在
     *
     * @param indexName 索引库名称
     * @return true 存在 false 不存在
     */
    public boolean isIndexExist(String indexName) {
        GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
        try {
            GetIndexResponse getIndexResponse = client.indices().get(getIndexRequest, RequestOptions.DEFAULT);
            Map<String, MappingMetadata> mappings = getIndexResponse.getMappings();
            return mappings != null;
        } catch (Exception e) {
            log.error("索引库 {} 不存在 错误原因：[{}]", indexName, e.getMessage());
            return false;
        }
    }

    /**
     * 查询指定索引库
     *
     * @param indexName 索引库名称
     * @return 索引库结构
     */
    public Map<String, MappingMetadata> getIndex(String indexName) {
        GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
        try {
            GetIndexResponse getIndexResponse = client.indices().get(getIndexRequest, RequestOptions.DEFAULT);
            Map<String, MappingMetadata> mappings = getIndexResponse.getMappings();
            return mappings;
        } catch (Exception e) {
            log.error("索引库 {} 不存在 错误原因：[{}]", indexName, e.getMessage());
        }
        return null;
    }

    /**
     * 新增文档数据
     *
     * @param indexName  索引库名称
     * @param sourceData 新增数据
     * @return 新增文档的全局唯一id
     */
    public String insertDocument(String indexName, String sourceData) {
        IndexRequest indexRequest = new IndexRequest();
        // 指定索引库
        indexRequest.index(indexName);
        // 指定新增数据
        indexRequest.source(sourceData, XContentType.JSON);

        try {
            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
            String id = indexResponse.getId();  // 文档唯一id
            log.info("新增文档数据成功  文档id：{}", id);
            return id;
        } catch (Exception e) {
            log.error("新增文档异常：索引库名称：{}, 新增数据：{}，错误信息：{}", indexName, sourceData, e.getMessage());
        }
        return null;
    }

    /**
     * 新增文档数据
     *
     * @param documentId 指定文档id
     * @param indexName  索引库名称
     * @param sourceData 新增数据
     * @return 新增文档的全局唯一id
     * @description: 当前操作属于 全量更新，修改文档也可以调用该方法，方法底层会删除指定文档后在新增
     */
    public String insertDocument(String documentId, String indexName, String sourceData) {
        IndexRequest indexRequest = new IndexRequest();
        if (documentId == null) {
            return insertDocument(indexName, sourceData);
        }
        // 指定文档id
        indexRequest.id(documentId);
        // 指定索引库
        indexRequest.index(indexName);
        // 指定新增数据
        indexRequest.source(sourceData, XContentType.JSON);

        try {
            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
            String id = indexResponse.getId();  // 文档唯一id
            log.info("新增文档数据成功  文档id：{}", id);
            return id;
        } catch (Exception e) {
            log.error("新增文档异常：索引库名称：{},文档id：{}，新增数据：{}，错误信息：{}", indexName, documentId, sourceData, e.getMessage());
        }
        return null;
    }

    /**
     * 新增文档数据
     *
     * @param documentId 指定文档id
     * @param indexName  索引库名称
     * @param sourceData 新增数据
     * @return 新增文档的全局唯一id
     * &#064;description:  当前操作属于 全量更新，修改文档也可以调用该方法，方法底层会删除指定文档后在新增
     */
    public String insertDocument(Long documentId, String indexName, String sourceData) {
        return insertDocument(documentId.toString(), indexName, sourceData);
    }

    /**
     * 获取文档数据
     *
     * @param documentId 文档id
     * @param indexName  索引库名称
     * @param aClass     指定文档类型
     * @return 文档数据 以JSON字符串形式返回
     */
    public <T> T getDocument(String documentId, String indexName , Class<T> aClass) {
        GetRequest getRequest = new GetRequest();
        getRequest.id(documentId);
        getRequest.index(indexName);

        try {
            GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
            String source = response.getSourceAsString();
            return JSONUtil.toBean(source,aClass);
        } catch (Exception e) {
            log.error("获取文档数据异常：文档id：{}，索引库名称：{}，异常信息：{}", documentId, indexName, e.getMessage());
        }
        return null;
    }
    /**
     * 获取文档数据
     *
     * @param documentId 文档id
     * @param indexName  索引库名称
     * @return 文档数据 以JSON字符串形式返回
     */
    public String getDocument(String documentId, String indexName) {
        GetRequest getRequest = new GetRequest();
        getRequest.id(documentId);
        getRequest.index(indexName);

        try {
            GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
            // 将文档已字符串形式返回
            return response.getSourceAsString();
        } catch (Exception e) {
            log.error("获取文档数据异常：文档id：{}，索引库名称：{}，异常信息：{}", documentId, indexName, e.getMessage());
        }
        return null;
    }

    /**
     * 删除文档
     *
     * @param documentId 文档id
     * @param indexName  索引库 名称
     * @return true 成功 false 失败
     */
    public boolean deleteDocument(String documentId, String indexName) {
        DeleteRequest deleteRequest = new DeleteRequest(indexName, documentId);
        try {
            DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
            if (response.status() == RestStatus.OK) {
                log.info("删除文档成功 文档id: {}",response.getId());
                return true;
            }
        } catch (Exception e) {
            log.error("删除文档异常：文档id：{}，索引库名称：{}，异常信息：{}", documentId, indexName, e.getMessage());
            throw new ElasticsearchException(e.getMessage());
        }
        return false;
    }

    /**
     * 删除指定索引库下全部文档
     *
     * @param indexName 索引库名称
     * @return true 成功 false 失败
     */
    public boolean deleteDocumentAll(String indexName) {
        Map<String, String> searchMatchAll = searchMatchAll(indexName);
        Set<String> keyed = searchMatchAll.keySet();
        for (String id : keyed) {
            boolean document = deleteDocument(id, indexName);
            if (!document) {
                return false;
            }
        }
        return true;
    }

    /**
     * 局部更新文档  只会更新给定的数据 全量更新需要执行 `insertDocument`
     *
     * @param documentId 文档id
     * @param indexName  索引库名称
     * @param updateData 待更新数据 field:text 结构，field = 文档中的字段，text = 更新的值
     * @return true 成功 false 失败
     * @description: 注意：局部更新文档只能更新文档中存在的字段，如果文档中不存在该字段，则不会更新，不会报错  推荐使用全量更新 insertDocument
     */
    @Deprecated
    public boolean updateDocument(String documentId, String indexName, String... updateData) {
        // 构建 删除文档request
        UpdateRequest updateRequest = new UpdateRequest(indexName, documentId);
        // 构建 更新参数 内部传递的为 任意数据 ， 每两个数据为一对 field：text
        // updateRequest.doc("更新参数");
        updateRequest.doc(updateData);
        // 发送请求
        try {
            client.update(updateRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            log.error("局部更新文档数据异常：文档id：{}，索引库名称：{}，更新参数：{}，异常信息：{}", documentId, indexName, updateData, e.getMessage());

        }
        return false;
    }

    /**
     * 查询指定索引库下的全部数据
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的数据 并且封装成实体类 默认只返回10条数据
     */
    public <T> EsResource<T> searchMatchAll(String indexName, Class<T> aClass) {
        return searchMatchAll(indexName, aClass, PageOf());
    }

    /**
     * 查询全部数据并根据指定字段进行排序
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    排序字段
     * @param type      排序类型
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的全部数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatchAll(String indexName, Class<T> aClass, String sorted, SortOrder type) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchAllQuery())  // 查询全部数据
                .sort(sorted, type);

        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("查询索引库全部数据异常 索引库名称：{}，待转换的实体类型：{},异常信息：{},", indexName, aClass, e.getMessage());
        }
        return null;
    }

    /**
     * 查询全部数据并根据指定字段进行排序后进行分页
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    排序字段
     * @param type      排序类型
     * @param page      分页对象
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的全部数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatchAll(String indexName, Class<T> aClass, String sorted, SortOrder type, Page page) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchAllQuery())  // 查询全部数据
                .sort(sorted, type)
                .from(page.getPageOn())
                .size(page.getPageSize());

        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("查询索引库全部数据异常 索引库名称：{}，待转换的实体类型：{},异常信息：{},", indexName, aClass, e.getMessage());

        }
        return null;
    }

    /**
     * 查询全部数据 并进行分页
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param page      分页对象
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的全部数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatchAll(String indexName, Class<T> aClass, Page page) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchAllQuery())  // 查询全部数据
                .from(page.getPageOn())
                .size(page.getPageSize());

        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("查询索引库全部数据异常 索引库名称：{}，待转换的实体类型：{},异常信息：{},", indexName, aClass, e.getMessage());

        }
        return null;
    }

    /**
     * 查询指定索引库下的全部数据
     *
     * @param indexName 索引库名称
     * @return Map<String, String> ： field为当前数据的唯一id  text为当前数据的json格式字符串
     */
    public Map<String, String> searchMatchAll(String indexName) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchAllQuery());  // 查询全部数据

        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            SearchHit[] searchHits = response.getHits().getHits();
            Map<String, String> map = new HashMap<>();
            for (SearchHit searchHit : searchHits) {
                String id = searchHit.getId();  // 当前数据的唯一id
                map.put(id, searchHit.getSourceAsString());
            }
            return map;
        } catch (Exception e) {
            log.error("查询索引库全部数据异常 索引库名称：{}，异常信息：{},", indexName, e.getMessage());

        }
        return null;
    }

    /**
     * match 单字段匹配
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param field     匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatch(String indexName, Class<T> aClass, String field, String text , Page page) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text))
                .from(page.pageOn)
                .size(page.pageSize);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 单字段匹配异常 索引库名称：{}，field：{}，text：{},待转换的实体类型：{},异常信息：{} ，"
                    , indexName, field, text, aClass, e.getMessage());

        }
        return null;
    }

    /**
     * match 单字段匹配  并且按照指定规则进行排序
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    待排序的字段
     * @param type      排序的规则
     * @param field     匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatch(String indexName, Class<T> aClass, String sorted, SortOrder type, String field, String text) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text))
                .sort(sorted, type);
        log.info(request.source().toString());
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 单字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，type：{}，待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorted, type, aClass, e.getMessage());
        }
        return null;
    }

    /**
     * match 单字段匹配  并且按照指定规则进行排序
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorts     排序的规则 key 为字段名，value 为排序规则 可指定多个字段排序
     * @param field     匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatch(String indexName, Class<T> aClass, Map<String, SortOrder> sorts, String field, String text) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text));
        for (Map.Entry<String, SortOrder> orderEntry : sorts.entrySet()) {
            request.source().sort(orderEntry.getKey(), orderEntry.getValue());
        }
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 单字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorts, aClass, e.getMessage());
        }
        return null;
    }

    /**
     * match 单字段匹配  并且按照指定规则进行排序  并进行分页处理
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    待排序的字段
     * @param type      排序的规则
     * @param page      分页对象
     * @param field     匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatch(String indexName, Class<T> aClass, String sorted, SortOrder type, String field, String text , Page page) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text))
                .sort(sorted, type)
                .from(page.getPageOn())
                .size(page.getPageSize());
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 单字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，type：{}，page:{},pageSize:{},待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorted, type, page.getPageOn(), page.getPageSize(), aClass, e.getMessage());

        }
        return null;
    }

    /**
     * match 单字段匹配  并且按照指定规则进行排序 并进行分页处理
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorts     排序的规则 key 为字段名，value 为排序规则 可指定多个字段排序
     * @param page      分页对象
     * @param field     匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMatch(String indexName, Class<T> aClass, Map<String, SortOrder> sorts, String field, String text , Page page) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text))
                .from(page.getPageOn())
                .size(page.getPageSize());
        for (Map.Entry<String, SortOrder> orderEntry : sorts.entrySet()) {
            request.source().sort(orderEntry.getKey(), orderEntry.getValue());
        }
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 单字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，pageOn:{},pageSize:{},待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorts, page.getPageOn(), page.getPageSize(), aClass, e.getMessage());

        }
        return null;
    }


    /**
     * multiMatch 多字段匹配
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param field     可变字符串参数，要匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMultiMatch(String indexName, Class<T> aClass, Page page ,String text, String... field) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.multiMatchQuery(text, field))
                .from(page.pageOn)
                .size(page.pageSize);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 多字段匹配异常 索引库名称：{}，field：{}，text：{}，异常信息：{} ，", indexName, field, text, e.getMessage());

        }
        return null;
    }

    /**
     * multiMatch 多字段匹配 并且按照指定规则进行排序
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    待排序的字段
     * @param type      排序的规则
     * @param field     可变字符串参数，要匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMultiMatch(String indexName, Class<T> aClass, String sorted, SortOrder type, String text, String... field) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.multiMatchQuery(text, field))
                .sort(sorted, type);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 多字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，type：{}，待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorted, type, aClass, e.getMessage());

        }
        return null;
    }

    /**
     * multiMatch 多字段匹配 并且按照指定规则进行排序
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorts     排序的规则 key 为字段名，value 为排序规则 可指定多个字段排序
     * @param field     可变字符串参数，要匹配的字段
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMultiMatch(String indexName, Class<T> aClass, Map<String, SortOrder> sorts, String text, String... field) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.multiMatchQuery(text, field));
        for (Map.Entry<String, SortOrder> orderEntry : sorts.entrySet()) {
            request.source().sort(orderEntry.getKey(), orderEntry.getValue());
        }
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 多字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorts, aClass, e.getMessage());

        }
        return null;
    }

    /**
     * multiMatch 多字段匹配 并且按照指定规则进行排序 然后分页
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorted    待排序的字段
     * @param type      排序的规则
     * @param field     可变字符串参数，要匹配的字段
     * @param page      分页对象
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMultiMatch(String indexName, Class<T> aClass, String sorted, SortOrder type, Page page, String text, String... field) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.multiMatchQuery(text, field))
                .sort(sorted, type)
                .from(page.getPageOn())
                .size(page.getPageSize());
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 多字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，type：{}，pageOn:{},pageSize:{},待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorted, type, page.getPageOn(), page.getPageSize(), aClass, e.getMessage());
        }
        return null;
    }

    /**
     * multiMatch 多字段匹配 并且按照指定规则进行排序 然后分页
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param sorts     排序的规则 key 为字段名，value 为排序规则 可指定多个字段排序
     * @param field     可变字符串参数，要匹配的字段
     * @param page      分页对象
     * @param text      要匹配的文本
     * @param <T>       泛型 文档的类型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchMultiMatch(String indexName, Class<T> aClass, Map<String, SortOrder> sorts, Page page, String text, String... field) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.multiMatchQuery(text, field))
                .from(page.getPageOn())
                .size(page.getPageSize());
        for (Map.Entry<String, SortOrder> orderEntry : sorts.entrySet()) {
            request.source().sort(orderEntry.getKey(), orderEntry.getValue());
        }
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 全文检索 多字段匹配异常 索引库名称：{}，field：{}，text：{} sorted：{}，pageOn:{},pageSize:{},待转换的实体类型：{},异常信息：{} ，",
                    indexName, field, text, sorts, page.getPageOn(), page.getPageSize(), aClass, e.getMessage());
        }
        return null;
    }

    /**
     * term 精确字段匹配
     * 精准匹配的底层是查询词条列表获取数据返回，由于的精准匹配我们输入的text值不会做分词
     * 也就是说 不会对text进行分词 而是直接拿text去词条列表寻找
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param field     待匹配的字段
     * @param text      待匹配的文本
     * @param <T>       泛型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchTerm(String indexName, Class<T> aClass, String field, String text) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.termQuery(field, text));
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 精确字段匹配异常 索引库名称：{} ，field：{}，text：{}，异常信息：{}", indexName, field, text, e.getMessage());

        }
        return null;
    }

    /**
     * range 字段进行范围匹配
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param field     待匹配的字段
     * @param max       最大值
     * @param min       最小值
     * @param <T>       泛型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchRange(String indexName, Class<T> aClass, String field, Long max, Long min) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.rangeQuery(field)
                        .gte(min)
                        .lte(max));
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 范围匹配异常 索引库名称：{} ，field：{}，异常信息：{}", indexName, field, e.getMessage());

        }
        return null;
    }

    /**
     * ids 根据文档id精准匹配
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param ids       文档id集合
     * @param <T>       泛型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchIds(String indexName, Class<T> aClass, List<String> ids) {
        String[] array = ids.toArray(String[]::new);
        return searchIds(indexName, aClass, array);
    }

    /**
     * ids 根据文档id精准匹配
     *
     * @param indexName 索引库名称
     * @param aClass    转换的类型
     * @param ids       文档id 可变参数
     * @param <T>       泛型
     * @return 当前索引库下的匹配数据 并且封装成实体类
     */
    public <T> EsResource<T> searchIds(String indexName, Class<T> aClass, String... ids) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.idsQuery().addIds(ids));
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(aClass, response);
        } catch (Exception e) {
            log.error("叶子查询 ids精准匹配异常 索引库名称：{}，ids：{}，异常信息：{} ", indexName, ids, e.getMessage());
        }
        return null;
    }

    /**
     * match 单字段匹配，高亮展示结果
     *
     * @param indexName        索引库名称
     * @param field            待匹配字段
     * @param text             待匹配文本
     * @param highlighterField 高亮展示字段
     * @param preTags          高亮字段的  before
     * @param postTags         高亮字段的 after
     * @return 当前索引库下的匹配数据 并且封装成实体类
     * @Description: 对某一个字段中 某一个词语需要做高亮展示 可以调用该方法，该方法会把在ES中该 字段的数据
     * 根据给定的待匹配文本 进行高亮渲染后 返回该字段被渲染之后的数据
     */
    public <T> EsResource<T> searchMatchHighlighter(String indexName, Class<T> aClass, String field, String text,
                                                    String highlighterField, String preTags, String postTags) {
        SearchRequest request = new SearchRequest(indexName);

        request.source()
                .query(QueryBuilders.matchQuery(field, text))
                .highlighter(new HighlightBuilder()
                        .field(highlighterField)
                        .preTags(preTags)
                        .postTags(postTags));

        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 获得查询返回的响应数据
            SearchHit[] searchHits = response.getHits().getHits();
            // 确保顺序不乱 HashMap会导致排序后的结果混乱
            Map<String , T> map = new LinkedHashMap<>(searchHits.length);
            // 获取源数据
            for (SearchHit searchHit : searchHits) {
                // 高亮结果
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                // 拿到我们渲染的高亮数据
                HighlightField highlightField = highlightFields.get(highlighterField);
                // 得到每一个对text字段渲染的数据
                Text[] fragments = highlightField.getFragments();
                StringBuilder sb = new StringBuilder();
                // 需要进行拼接后返回字符串
                Arrays.stream(fragments).forEach(sb::append);
                // 拿到高亮数据
                String highlighter = sb.toString();
                // 将源数据转换为对象进行操作
                String sourceAsString = searchHit.getSourceAsString();
                T bean = JSONUtil.toBean(sourceAsString, aClass);
                // 利用反射操作将高亮之后的数据 替换掉原始数据
                Class<?> beanClass = bean.getClass();
                // 得到对应的set方法   利用StringUtils工具类的capitalize方法 底层则是将首字母改为大写 以便获取对应的set方法
                Method method = beanClass.getMethod("set" + StringUtils.capitalize(highlighterField) , String.class);
                // 屏蔽修饰符
                method.setAccessible(false);
                // 调用set方法 将高亮数据替换为原始数据
                method.invoke(bean,highlighter);
                map.put(searchHit.getId(), bean);
            }
            long value = response.getHits().getTotalHits().value;
            float maxScore = response.getHits().getMaxScore();
            return new EsResource<>(value, map , maxScore);
        } catch (Exception e) {
            log.error("高亮展示数据异常：索引库：{}，field：{}，text：{}，高亮展示字段：{}，before：{}，after：{}",
                    indexName, field, text, highlighterField, preTags, postTags);

        }
        return null;
    }

    /**
     * 解析响应数据 SearchResponse
     *
     * @param aClass   转换的实体类型
     * @param response SearchResponse
     * @param <T>      泛型
     * @return 封装为EsResource<T>进行返回
     */
    private static <T> EsResource<T> parseResponse(Class<T> aClass, SearchResponse response) {
        if (aClass == null) {
            throw new ElasticsearchException("实体类型不可为空");
        }
        SearchHits responseHits = response.getHits();   // 获取响应数据
        SearchHit[] source = responseHits.getHits();    // 获取响应体内的源数据
        Map<String , T> map = new LinkedHashMap<>(source.length);  // 确保顺序不乱 HashMap会导致排序后的结果混乱
        for (SearchHit searchHit : source) {
            // 将源数据已字符串形式返回
            String sourceAsString = searchHit.getSourceAsString();
            T bean = JSONUtil.toBean(sourceAsString, aClass);
            map.put(searchHit.getId(), bean);
        }
        long value = responseHits.getTotalHits().value;
        float maxScore = responseHits.getMaxScore();
        return new EsResource<>(value, map , maxScore);
    }

    /**
     * 获取指定类对应的索引库名称
     *
     * @param aclass 类字节码文件
     * @return 索引库名称
     */
    public String findIndexNameByClassZ(Class aclass) {
        EsProperties properties = this.getProperties();
        Class currentClass = aclass;
        String indexName;
        while (true){
            if (currentClass == null){
                throw new ElasticsearchException("索引库不存在");
            }
            String thisName = currentClass.getSimpleName().toLowerCase();
            indexName = properties.getIndexNames().get(thisName);
            if (indexName != null){
                break;
            }
            currentClass = currentClass.getSuperclass();

        }
        return indexName;
    }

    @Data
    static class Page {
        private Integer pageOn;
        private Integer pageSize;
        private Page(Integer pageOn, Integer pageSize) {
            this.pageSize = pageSize;
            this.pageOn = getStart(pageOn);
        }
        private Integer getStart(Integer pageOn) {
            return (pageOn - 1) * this.pageSize;
        }
    }

    /**
     * 快速生成page对象 用于分页的快速操作
     * @param pageOn 待查询页码
     * @param pageSize 每页展示数
     * @return page对象
     */
    public Page PageOf(Integer pageOn, Integer pageSize){
        if (pageOn == null){
            pageOn = 1;
        }
        if (pageSize == null){
            pageSize = 10;
        }
        return new Page(pageOn, pageSize);
    }

    /**
     * 快速生成page对象 用于分页的快速操作
     * @return 默认为 1 - 10
     */
    public Page PageOf() {
        return PageOf(1, 10);
    }


}
